======= >>>>>>> master
The Makie.jl webpage says
<<<<<<< HEADFrom the japanese word Maki-e, which is a technique to sprinkle lacquer with gold and silver powder. Data is the gold and silver of our age, so let's spread it out beautifully on the screen!
This tutorial will cover how to use Makie to make the basic plots discussed in these notes. Makie is capable of much more. For example, see BeautifulMakie for numerous examples; see AlgebraOfGraphics which presents an interface for the familiar graphics of statistics. Documentation for Makie may be found at Makie.
Makie draws graphics onto a canvas. There are GLMakie, WGLMakie, and CairoMakie backends for different types of canvases. In the following, we have used GLMakie. GLMakie and WGLMakie allow for interactive graphics; CairoMakie is useful for static graphics, as in this tutorial.
GLMakie is loaded as other packages; by loading thepackage (which must have been installed) the plotting commands from Makie are also exported.
using GLMakie =======From the Jpanese word Maki-e, which is a technique to sprinkle lacquer with gold and silver powder. Data is basically the gold and silver of our age, so let's spread it out beautifully on the screen!
Makieitself is a metapackage for a rich ecosystem. We show how to use the interface provided byAbstractPlottingand theGLMakiebackend to produce the familiar graphics of calculus. We do not discuss theMakieLayoutpackage which provides a means to layout multiple graphics and add widgets, such as sliders and buttons, to a layout. We do not discussMakieRecipes. ForPlots, there are "recipes" that make some of the plots more straightforward. We do not discuss theAlgebraOfGraphicswhich presents an interface for the familiar graphics of statistics.Scenes
Makie draws graphics onto a canvas termed a "scene" in the Makie documentation. There are
GLMakie,WGLMakie, andCairoMakiebackends for different types of canvases. In the following, we have usedGLMakie.WGLMakieis useful for incorporatingMakieplots into web-based technologies.We begin by loading our two packages:
using AbstractPlotting using GLMakie #using WGLMakie; WGLMakie.activate!() #AbstractPlotting.set_theme!(scale_figure=false, resolution = (480, 400)) >>>>>>> master<<<<<<< HEADWe also load the
CalculusWithJuliafuncition to utilize a few of its convenience functions.using CalculusWithJuliaThe
Makiedevelopers have workarounds for the delayed time to first plot, but without utilizing these the time to load the package and produce the first plot is lengthy.Points (
scatter)The task of plotting points, say $(1,2)$, $(2,3)$, $(3,2)$ can be done different ways. Most plotting packages, and
=======Makieis no exception, allow the following: form vectors of the $x$ and $y$ values then plot those withscatter:The
Makiedevelopers have workarounds for the delayed time to first plot, but without utilizing these the time to load the package is lengthy.A scene is produced with
Scene()or through a plotting primitive:scene = Scene()ERROR: UndefVarError: Scene not definedWe see next how to move beyond the blank canvas.
Points (
scatter)The task of plotting the points, say $(1,2)$, $(2,3)$, $(3,2)$ can be done different ways. Most plotting packages, and
>>>>>>> masterMakieis no exception, allow the following: form vectors of the $x$ and $y$ values then plot those withscatter:xs = [1,2,3] ys = [2,3,2] <<<<<<< HEAD fap = scatter(xs, ys)![]()
The
scatterfunction creates and returns aFigureAxisPlotobject, which when displayed shows the plot.The more generic
plotfunction can also be used for this task:plot(xs, ys)======= scatter(xs, ys)
ERROR: UndefVarError: scatter not defined
The scatter function creates and returns a Scene object, which when displayed shows the plot.
The more generic plot function can also be used for this task.
Point2, Point3When learning about points on the Cartesian plane, a "t"-chart is often produced:
x | y
-----
1 | 2
2 | 3
3 | 2
<<<<<<< HEAD
The scatter usage above used the columns for the data. The rows are associated with the points, and these too can be used to produce the same graphic in Makie.
Rather than make vectors of $x$ and $y$ (and optionally $z$) coordinates, it is more idiomatic to create a vector of "points." Makie utilizes a Point type to store a 2 or 3 dimensional point. The Point2 and Point3 constructors will be utilized.
Makie uses a GPU, when present, to accelerate the graphic rendering. GPUs employ 32-bit numbers. Julia uses an f0 to indicate 32-bit floating points. Hence the alternate types Point2f0 to store 2D points as 32-bit numbers and Points3f0 to store 3D points as 32-bit numbers are seen in the documentation for Makie.
We can plot a vector of points in as direct manner as vectors of their coordinates:
=======The scatter usage above used the columns. The rows are associated with the points, and these too can be used to produce the same graphic. Rather than make vectors of $x$ and $y$ (and optionally $z$) coordinates, it is more idiomatic to create a vector of "points." Makie utilizes a Point type to store a 2 or 3 dimensional point. The Point2 and Point3 constructors will be utilized.
Makie uses a GPU, when present, to accelerate the graphic rendering. GPUs employ 32-bit numbers. Julia uses an f0 to indicate 32-bit floating points. Hence the alternate types Point2f0 to store 2D points as 32-bit numbers and Points3f0 to store 3D points as 32-bit numbers are seen in the documentation for Makie.
We can plot vector of points in as direct manner as vectors of their coordinates:
>>>>>>> masterpts = [Point2(1,2), Point2(2,3), Point2(3,2)] scatter(pts)<<<<<<< HEAD
The conversion from pairs of vectors to a vector of points can be easily done through broadcasting:
Point2.(xs, ys)
3-element Vector{GeometryBasics.Point2{Int64}}:
[1, 2]
[2, 3]
[3, 2]
The following can also be done, where values are zipped together then broadcast over:
Point2.(zip(xs,ys))
3-element Vector{GeometryBasics.Point2{Int64}}:
[1, 2]
[2, 3]
[3, 2]
That is more work, so isn't suggested, but motivates the use of unzip from the CalculusWithJulia package to go from a vector of points to a pair of vectors:
vec_pts = Point2.(zip(xs,ys)) pr_vecs = unzip(vec_pts) # (xs, ys)
([1, 2, 3], [2, 3, 2])
The unzip function is not very magical, it just goes over each dimension of the point and plucking out the matching $x$, $y$, $\dots$ values:
unzip(vs) = Tuple([[v[j] for v in vs] for j in eachindex(first(vs))])
The pair of vectors can be easily plotted. Here we use splatting (the ...) to break the pair into its pieces to pass to scatter:
scatter(pr_vecs...)
A typical usage is to generate points from some vector-valued function. Say we have a parameterized function, r, taking $R$ into $R^2$ defined by:
ERROR: UndefVarError: scatter not defined
A typical usage is to generate points from some vector-valued function. Say we have a parameterized function r taking $R$ into $R^2$ defined by:
r(t) = [sin(t), cos(t)]
r (generic function with 1 method)
Then broadcasting values gives a vector of vectors, each identified with a point:
ts = [1,2,3] r.(ts)
3-element Vector{Vector{Float64}}:
[0.8414709848078965, 0.5403023058681398]
[0.9092974268256817, -0.4161468365471424]
[0.1411200080598672, -0.9899924966004454]
We can broadcast Point2 over this to create a vector of Point objects:
pts = Point2.(r.(ts))
3-element Vector{GeometryBasics.Point2{Float64}}:
[0.8414709848078965, 0.5403023058681398]
[0.9092974268256817, -0.4161468365471424]
[0.1411200080598672, -0.9899924966004454]
These then can be plotted directly:
scatter(pts)<<<<<<< HEAD
The plotting of points in three dimesions is essentially the same, save the use of Point3 instead of Point2.
ERROR: UndefVarError: scatter not defined
The ploting of points in three dimesions is essentially the same, save the use of Point3 instead of Point2.
r(t) = [sin(t), cos(t), t] ts = range(0, 4pi, length=100) pts = Point3.(r.(ts)) <<<<<<< HEAD scatter(pts, markersize=50)
Alternatively, this pattern will work; splatting is used to specify the xs, ys, and zs to scatter:
scatter(unzip(r.(ts))..., markersize=50)
At the time of writing, the markersize for GLMakie and for CairoMakie varies dramatically. The figure above in CairoMakie is better suited for a markersize of 10.
ERROR: UndefVarError: scatter not defined
To plot points generated in terms of vectors of coordinates, the component vectors must be created. The "t"-table shows how, simply loop over each column and add the corresponding $x$ or $y$ (or $z$) value. This utility function does exactly that, returning the vectors in a tuple.
unzip(vs) = Tuple([vs[j][i] for j in eachindex(vs)] for i in eachindex(vs[1]))
unzip (generic function with 1 method)
(The functionality is essentially a reverse of the zip function, hence the name.)
We might have then:
scatter(unzip(r.(ts))...)
ERROR: UndefVarError: scatter not defined
where splatting is used to specify the xs, ys, and zs to scatter.
(Compare to scatter(Point3.(r.(ts))) or scatter(Point3∘r).(ts)).)
A point is drawn with a "marker" with a certain size and color. These attributes can be adjusted, as in the following:
scatter(xs, ys, marker=[:x,:cross, :circle], markersize=25, color=:blue)<<<<<<< HEAD
ERROR: UndefVarError: scatter not defined>>>>>>> master
Marker attributes include
marker a symbol, shape. A single value will be repeated. A vector of values of a matching size will specify a marker for each point.
marker_offset offset coordinates
markersize size (radius pixels) of marker
text)Text can be placed at a point, as a marker is. To place text the desired text and a position need to be specified.
For example:
<<<<<<< HEAD ts = 1:12 roman_labels = ["I","II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII"] thetas = pi/2 .- ts*(2pi/12) pts = Point2.(cos.(thetas), sin.(thetas)) szes = 20 * ones(Int, 12) szes[[3,6,9,12]] *= 1.25 transparent_red = RGBAf0(1.0,0.0, 0.0, 0.5) fig,ax,plt = scatter(pts, markersize=60, color=transparent_red) for (txt, pt, sz, α) in zip(roman_labels, pts, szes, thetas) text!(ax, txt, position=pt, textsize=sz, rotation=α-pi/2, align = (:center, :center)) end fig
The graphic shows that position positions the text, align is used to adjust the text relative to its positioning, textsize adjusts the displayed size, and rotation adjusts the orientation.
ERROR: UndefVarError: scatter not defined
The graphic shows that position positions the text, textsize adjusts the displayed size, and rotation adjusts the orientation.
Attributes for text include:
position to indicate the position. Either a Point object, as above, or a tuple
align Specify the text alignment through (:pos, :pos), where :pos can be :left, :center, or :right.
rotation to indicate how the text is to be rotated
textsize the font point size for the text
font to indicate the desired font
The basic plot of univariate calculus is the graph of a function $f$ over an interval $[a,b]$. This is implemented using a familiar strategy: produce a series of representative values between $a$ and $b$; produce the corresponding $f(x)$ values; plot these as points and connect the points with straight lines. The lines function of AbstractPlotting will do the last step.
By taking a sufficient number of points within $[a,b]$ the connect-the-dot figure will appear curved, when the function is.
To create regular values between a and b either the range function, the related LinRange function, or the range operator (a:h:b) are employed.
For example:
f(x) = sin(x) a, b = 0, 2pi xs = range(a, b, length=250) lines(xs, f.(xs))<<<<<<< HEAD
ERROR: UndefVarError: lines not defined>>>>>>> master
Or
f(x) = cos(x) a, b = -pi, pi xs = a:pi/100:b lines(xs, f.(xs))<<<<<<< HEAD
As with scatter, lines returns a FigureAxisPlot object that produces a graphic when displayed.
As with scatter, lines can can also be drawn using a vector of points:
lines([Point2(x, fx) for (x,fx) in zip(xs, f.(xs))])
(Though the advantage isn't clear here, this will be useful when the points are more naturally generated.)
When a y value is NaN or infinite, the connecting lines are not drawn:
xs = 1:5 ys = [1,2,NaN, 4, 5] lines(xs, ys)
As with other plotting packages, this is useful to represent discontinuous functions, such as what occurs at a vertical asymptote.
There are a few conveniences to aid the construction of a plot of a function $f$. First, the broadcasting can be done implicitly by passing a function as the second argument:
=======ERROR: UndefVarError: lines not defined
As with scatter, lines returns a Scene object that produces a graphic when displayed.
As with scatter, lines can can also be drawn using a vector of points:
lines([Point2(x, fx) for (x,fx) in zip(xs, f.(xs))])
(Though the advantage isn't clear here, this will be useful when the points are more naturally generated.)
When a y value is NaN or infinite, the connecting lines are not drawn:
xs = 1:5
ys = [1,2,NaN, 4, 5]
lines(xs, ys)
As with other plotting packages, this is useful to represent discontinuous functions, such as what occurs at a vertical asymptote.
lines!, scatter!, ...)To add or modify a scene can be done using a mutating version of a plotting primitive, such as lines! or scatter!. The names follow Julia's convention of using an ! to indicate that a function modifies an argument, in this case the scene.
Here is one way to show two plots at once:
>>>>>>> masterxs = range(0, 2pi, length=100) <<<<<<< HEAD plot(xs, sin)
Second, a plot recipe is provided so that the notation plot(a..b, f) will generate the xs values and apply f to each:
plot(-pi..pi, f)
The .. notation comes from the IntervalSets package.
lines!, scatter!, ...)To add to or modify a scene can be done using a mutating version of a plotting primitive, such as lines! or scatter!. The names follow Julia's convention of using an ! to indicate that a function modifies an argument, in this case the axis of the plot.
Here is one way to show two graphs at once:
xs = range(0, 2pi, length=100) paf = lines(xs, sin.(xs)) lines!(xs, cos.(xs)) paf
The above uses the current axis. To be explicit, we can get the axis from a plot on construction, as follows:
fig, axis, plt = lines(xs, sin.(xs)) lines!(axis, xs, cos.(xs)) fig
Alternatively, if fap is the returned value of lines then fap.axis refers to the axis.
We will see soon how to modify the line attributes so that the curves can be distinguished as desired.
Here we see the alternate way to plot a function and its tangent line, the latter found using the tangent function from CalculusWithJulia.
f(x) = x^x a, b = 0, 2 c = 0.5 fap = plot(a..b, f) plot!(a..b, tangent(f,c), color=:blue) scatter!([Point2(c, f(c))]) fap
To see what points are used to construct a graph of a function when using the adaptively chosen points returned by CalculusWithJulia.PlotUtils.adapted_grid, we have:
f(x) = 2exp(x/2)*(sin(5x) + sin(15x)) xs,ys = CalculusWithJulia.PlotUtils.adapted_grid(f, (0,1)) fig, ax, plt = lines(xs, ys) scatter!(ax, xs, ys, markersize = 10) fig
The current axis will have data limits that can be of interest. The following indicates how they can be retrieved to get the limits of the displayed x values.
ERROR: UndefVarError: lines not defined
We will see soon how to modify the line attributes so that the curves can be distinguished.
The following shows the construction details in the graphic, and that the initial scene argument is implicitly assumed:
xs = range(0, 2pi, length=10) lines(xs, sin.(xs)) scatter!(xs, sin.(xs), markersize=10)
ERROR: UndefVarError: lines not defined
The current scene will have data limits that can be of interest. The following indicates how they can be manipulated to get the limits of the displayed x values.
xs = range(0, 2pi, length=200) <<<<<<< HEAD fap = plot(xs, sin.(xs)) rect = fap.axis.finallimits[] ======= scene = plot(xs, sin.(xs)) rect = scene.data_limits[] # get limits for g from f >>>>>>> master a, b = rect.origin[1], rect.origin[1] + rect.widths[1]<<<<<<< HEAD
(-0.31415927f0, 6.597345f0) =======ERROR: UndefVarError: plot not defined >>>>>>> masterIn the output it can be discerned that the values are 32-bit floating point numbers and yield a slightly larger interval than specified in
<<<<<<< HEADxs.This example shows how to adjust the limits programmatically once the graphic is rendered using
xlims!andylims!. The task is to visualize a rational function.f(x) = (x-1)*(x-3)/(x-2) fap = plot(0..4, f)![]()
The asymptote suggests trimming the visible part of the
yaxis. Theylims!function takes an axis object and new limits:ylims!(fap.axis, (-10, 10)) fap![]()
The artifact of the vertical line at $2$ is annoying. Adjusting the $x$ axis can avoid it to some extent:
xlims!(fap.axis, (2.05, 4)) fap![]()
Multiple plots per figure
An alternative to two graphs on one plot, is having two or more plots arranged for comparison. For this, we introduce the underlying
FigureandAxiscommands, which are present in theFigureAxisPlotobjects returned by the plotting constructors above.A
Figureobject is constructed byFigure(). AFigureobject contains the top-level scene, a layout object, as used below, and a list of items placed into. For layout purposes, a figure elegantly repurposes matrix notation (fig[row,col]) to specify areas of the figure for subplots. Theroworcolindices may be single integers or a range (e.g.1:2) if the subplot is to occupy more than one cell.A figure can be used immediately for plot layout; we pass the cell position to the plot constructor. The first two plots in this example show how direct this can be; the second two illustrate how ranges can be used to plot over multiple cells and how sub-layout creation using repeated
getindexnotation for matrices is possible:fig = Figure() plot(fig[1,1], -pi/3..pi/3, tan) plot(fig[1,2], -pi/3..pi/3, sec) plot(fig[2,1:2][1,1], -pi..pi, sin) plot(fig[2,1:2][2,1], -pi..pi, cos) fig![]()
For plotting multiple layers onto a single plot an
Axisobject is used. AnAxisobject is created byAxis(), as below.The plotting of a function and its first two derivatives provides another example. The $x$-axes should be aligned to compare values, but the scale of the $y$ values typically means trying to display all on the same plot will be problematic.
f(x) = (x+2)*(x-1)*(x-2)^2*(x-3)*(x-5) fig = Figure() ax = Axis(fig[1,1], title="f") ax1 = Axis(fig[2,1], title="f'") ax2 = Axis(fig[3,1], title="f''") a, b = -2.25, 5 plot!(ax, a..b, f) plot!(ax1, a..b, f') plot!(ax2, a..b, f'') linkxaxes!(ax, ax1, ax2) hidexdecorations!(ax) hidexdecorations!(ax1) fig![]()
To link the $x$ axes there are functions
linkaxes!,linkxaxes!, andlinkyaxes!. We chose to link jus the $x$ axes, leaving the $y$ axes scale to stretch to show the function. Following the Layout Tutorial some of the thexdecorations are hidden, so the reader isn't seeing duplicated axis ticks. We will discuss more how to add titles, but we see above that thetitlekeyword forAxiswill add a title.Attributes
In a prior example, we added the argument
=======color=:blueto theplot!call, as an instruction to set an attribute for the the plotted line. Lines have other attributes that allow different lines to be distinguished, as above where color is used to discriminate between different graphs.As an example, this shows how to add the tangent line to a graph. The slope of the tangent line being computed by
ForwardDiff.derivative.using ForwardDiff f(x) = x^x a, b= 0, 2 c = 0.5 xs = range(a, b, length=200) tl(x) = f(c) + ForwardDiff.derivative(f, c) * (x-c) scene = lines(xs, f.(xs)) lines!(scene, xs, tl.(xs), color=:blue)ERROR: UndefVarError: lines not definedAttributes
In the last example, we added the argument
>>>>>>> mastercolor=:blueto thelines!call. This set an attribute for the line being drawn. Lines have other attributes that allow different ones to be distinguished, as above where colors indicate the different graphs.Other attributes can be seen from the help page for
lines, and include:
color set with a symbol, as above, or a string
linestyle available styles are set by a symbol, one of :dash, :dot, :dashdot, or :dashdotdot.
linewidth width of line
transparency the alpha value, a number between $0$ and $1$, smaller numbers for more transparent.
Labels are another attribute and are used for legends, which are constructed by axislegend.
Here is an example:
fap = plot(0..pi, sin, label="sine", color=:blue) plot!(0..pi, cos, label="cosine", color=:red) axislegend("Trig. functions", position=:rt) # right top fap
As an aside, for mathematical markup, $\LaTeX$ can be used:
using LaTeXStrings fap = plot(0..pi, x-> sin(x^2), color=:blue, label=L"\sin(x^2)") plot!(0..pi, x-> sin(x)^2, color=:red, label=L"\sin(x)^2") axislegend("Functions", position=:lb) fap
Attributes of the axis include the titles and $x$- and $y$-labels, the limits that define the coordinates being displayed, the presentation of tick marks, etc.
The attributes can be accessed using the dot notation (getproperty). This example illustrates:
fap = plot(0..pi, cos) fap.axis.title = "Cosine" fap.axis.titlesize=32 fap.axis.xlabel = "x" fap.axis.xlabelsize=24 fap.axis.ylabel = "cos(x)" fap.axis.ylabelsize = 24 fap
These values can be set through the constructor - and not added after the fact - if that is convenient:
plot(0..pi, sin, figure=(backgroundcolor=:gray90,), axis = (title = "Sine", xlabel="x", xlabelsize=24, ylabel="sin(x)", ylabelsize=24, xticks=[0, pi/2, pi]) )
The values are named tuples. The figure value used an extra comma after :gray90, as that is needed to construct a tuple with a single value (to distinguish the meaning of the parentheses from their use for grouping).
The attributes may be single values or vectors of values with matching size. To illustrate, we make a simple plot of a straight line using $3$ points:
xs = [1,2,3] ys = [1,2,3] cols=[:red, :green, :blue] fig, ax, plt = lines(xs, ys, color=cols, linewidth=10) scatter!(ax, xs, ys, markersize=25) fig
This graph has two line segments between the three points, so it could be only two colors would be needed. Instead we see Makie blends the colors nicely.
For a more interesting plot, in the same vein, using the CalculusWithJulia.identify_colors(f, xs) function we can easily plot when a function is positive:
f(x) = (x-1)*(x-2)*(x-3) xs = range(0, 4, length=251) ys = f.(xs) cols = CalculusWithJulia.identify_colors(f, xs) lines(xs, ys, color=cols, linewidth=10)
The above shows how a single line can have multiple colors. More common is in a plot, multiple lines each have different colors. By default this is done by the palette attribute of the Axis object.
A legend can also be used to help identify different curves on the same graphic, though this is not illustrated. There are examples in the Makie gallery.
Attributes of the scene include any titles and labels, the limits that define the coordinates being displayed, the presentation of tick marks, etc.
The title function can be used to add a title to a scene. The calling syntax is title(scene, text).
To set the labels of the graph, there are "shorthand" functions xlabel!, ylabel!, and zlabel!. The calling pattern would follow xlabel!(scene, "x-axis").
The plotting ticks and their labels are returned by the unexported functions tickranges and ticklabels. The unexported xtickrange, ytickrange, and ztickrange; and xticklabels, yticklabels, and zticklabels return these for the indicated axes.
These can be dynamically adjusted using xticks!, yticks!, or zticks!.
pts = [Point2(1,2), Point2(2,3), Point2(3,2)] scene = scatter(pts) title(scene, "3 points") ylabel!(scene, "y values") xticks!(scene, xtickrange=[1,2,3], xticklabels=["a", "b", "c"])
ERROR: UndefVarError: scatter not defined
To set the limits of the graph there are shorthand functions xlims!, ylims!, and zlims!. This might prove useful if vertical asymptotes are encountered, as in this example:
f(x) = 1/x a,b = -1, 1 xs = range(-1, 1, length=200) scene = lines(xs, f.(xs)) ylims!(scene, (-10, 10)) center!(scene)
ERROR: UndefVarError: lines not defined
A space curve is a plot of a function $f:R^2 \rightarrow R$ or $f:R^3 \rightarrow R$.
To construct a curve from a set of points, we have a similar pattern in both $2$ and $3$ dimensions:
r(t) = [sin(2t), cos(3t)] ts = range(0, 2pi, length=200) pts = Point2.(r.(ts)) # or (Point2∘r).(ts) lines(pts)<<<<<<< HEAD
ERROR: UndefVarError: lines not defined>>>>>>> master
Or
r(t) = [sin(2t), cos(3t), t] ts = range(0, 2pi, length=200) pts = Point3.(r.(ts)) lines(pts)<<<<<<< HEAD
ERROR: UndefVarError: lines not defined>>>>>>> master
Alternatively, vectors of the $x$, $y$, and $z$ components can be produced and then plotted using the pattern lines(xs, ys) or lines(xs, ys, zs). For example, using unzip, as above, we might have done the prior example with:
xs, ys, zs = unzip(r.(ts)) lines(xs, ys, zs)<<<<<<< HEAD
arrows)ERROR: UndefVarError: lines not defined
arrows)A tangent vector along a curve can be drawn quite easily using the arrows function. There are different interfaces for arrows, but we show the one which uses a vector of positions and a vector of "vectors". For the latter, we utilize the derivative function from ForwardDiff:
using ForwardDiff r(t) = [sin(t), cos(t)] # vector, not tuple ts = range(0, 4pi, length=200) <<<<<<< HEAD fap = lines(Point2.(r.(ts)), color=:blue) ======= scene = Scene() lines!(scene, Point2.(r.(ts))) >>>>>>> master nts = 0:pi/4:2pi us = r.(nts) dus = ForwardDiff.derivative.(r, nts) <<<<<<< HEAD arrows!(Point2.(us), Point2.(dus)) fap
ERROR: UndefVarError: Scene not defined>>>>>>> master
In 3 dimensions the differences are minor:
<<<<<<< HEAD r(t) = [sin(2t), cos(3t), t] ts = range(0, 2pi, length=200) fap = lines(Point3.(r.(ts)), color=:blue) nts = pi/6:pi/6:11pi/6 us = r.(nts) dus = ForwardDiff.derivative.(r, nts) arrows!(Point3.(us), Point3.(dus)) fap
ERROR: UndefVarError: Scene not defined
Attributes for arrows include
arrowsize to adjust the size
lengthscale to scale the size
arrowcolor to set the color
arrowhead to adjust the head
arrowtail to adjust the tail
The graph of an equation is the collection of all $(x,y)$ values satisfying the equation. This is more general than the graph of a function, which can be viewed as the graph of the equation $y=f(x)$. An equation in $x$-$y$ can be graphed if the set of solutions to a related equation $f(x,y)=0$ can be identified, as one can move all terms to one side of an equation and define $f$ as the rule of the side with the terms.
The MDBM (Multi-Dimensional Bisection Method) package can be used for the task of characterizing when $f(x,y)=0$. (Also IntervalConstraintProgramming can be used.) We begine by loading the package (avoiding naming conflicts) and define a helper function:
import MDBM import MDBM: MDBM_Problem, solve!, connect, getinterpolatedsolution function implicit_equation(f, axes...; iteration::Int=4, constraint=nothing) =======Implicit equations (2D)
The graph of an equation is the collection of all $(x,y)$ values satisfying the equation. This is more general than the graph of a function, which can be viewed as the graph of the equation $y=f(x)$. An equation in $x$-$y$ can be graphed if the set of solutions to a related equation $f(x,y)=0$ can be identified, as one can move all terms to one side of an equation and define $f$ as the rule of the side with the terms.
The MDBM (Multi-Dimensional Bisection Method) package can be used for the task of characterizing when $f(x,y)=0$. (Also
IntervalConstraintProgrammingcan be used.) We first wrap its interface and then define a "plot" recipe (through method overloading, not throughMakieRecipes).using MDBMERROR: ArgumentError: Package MDBM not found in current path: - Run `import Pkg; Pkg.add("MDBM")` to install the MDBM package.function implicit_equation(f, axes...; iteration::Int=4, constraint=nothing) >>>>>>> master axes = [axes...] if constraint == nothing prob = MDBM_Problem(f, axes) else prob = MDBM_Problem(f, axes, constraint=constraint) end solve!(prob, iteration) prob endimplicit_equation (generic function with 1 method)The
implicit_equationfunction is just a simplified wrapper for theMDBM_Probleminterface. It creates an object to be plotted in a manner akin to:f(x,y) = x^3 + x^2 + x + 1 - x*y # solve x^3 + x^2 + x + 1 = x*y <<<<<<< HEAD ie = implicit_equation(f, -5:5, -10:10);The function definition is straightforward. The limits for
xandyare specified in the above using ranges. This specifies the initial grid of points for the apdaptive algorithm used byMDBMto identify solutions.To visualize the output, we use two functions depending on the dimension. These could be more nicely packaged.
# 2D plot function implicit_plot_2D!(axis, m::MDBM_Problem; color=:black, kwargs...) mdt= connect(m) for i in 1:length(mdt) dt = mdt[i] P1 = getinterpolatedsolution(m.ncubes[dt[1]], m) P2 = getinterpolatedsolution(m.ncubes[dt[2]], m) lines!(axis, [P1[1],P2[1]],[P1[2],P2[2]], color=color, kwargs...) end axis endimplicit_plot_2D! (generic function with 1 method)To plot using the above, we need a blank axis. This
AxisandFigurecombination can be used to construct one:fig = Figure() ax = Axis(fig[1,1]) implicit_plot_2D!(ax, ie) fig![]()
We see that the equation
iehas two pieces. (This is known as Newton's trident, as Newton was interested in this form of equation.)Scalar functions $z = f(x,y)$ or $w=f(x,y,z)$
Function of two or three variables and a scalar output require different graphs to visualize them. There are 2D graphs and 3D graphs
Contour plots
A contour plot shows implicit plots for $f(x,y)=c$ for a collection of
cvalues, the levels.To illustrate, we borrow an interesting function from Beautiful Makie.
function tα_qubit(β, ψ1, ψ2, fα, f) 2 + 2*β - cos(ψ1) - cos(ψ2) - 2*β*cos(π*fα)*cos(2*π*f + π*fα - ψ1- ψ2) end f(x,y) = tα_qubit(0.61, x, y, 0.2, 0.1) xs = range(0, 4pi, length=100) ys = range(-2pi, 2pi, length=100) zs = f.(xs, ys') contour(xs, ys, zs)![]()
The generation of the values in
zsuses broadcasting and is a pattern worth remembering. An alternative, that may be easier to read, is to use a comprehension. We downsample here to illustrate:xs′ = range(0, 4pi, length=5) ys′ = range(-2pi, 2pi, length=5) [f(x,y) for x in xs′, y in ys′]5×5 Matrix{Float64}: 0.915 3.525 0.915 3.525 0.915 3.525 4.915 3.525 4.915 3.525 0.915 3.525 0.915 3.525 0.915 3.525 4.915 3.525 4.915 3.525 0.915 3.525 0.915 3.525 0.915However, for even more convenience, the syntax
contour(xs, ys, f)is also possible.Contour plots can have the number of levels specified (the default is
levels=5) and a colormap given to adjust the colors.In addition to lines drawn for each displayed level, contour plots can be colored in. The
contourffunction is for this task:contourf(xs, ys, f)![]()
An alternative visualzation using color to replace contour lines is a heatmap, which are produced by
heatmap. The calling syntax is similar tocontourfandsurface:heatmap(xs, ys, f)![]()
This graph shows peaks and valleys through "hotspots" on the graph. By layering a contour map on top an alternate to the filled contour graph can be constructed.
Surface plots
A surface plot is a two dimensional plot visualized in 3 dimensions. They arise in different manners.
Plots of scalar functions $z=f(x,y)$
A 3-dimensional plot of
======= ie = implicit_equation(f, -5:5, -10:10)z=f(x,y)is a plot of the solutions(x,y,z)filled in through some means.
ERROR: UndefVarError: MDBM_Problem not defined
The function definition is straightforward. The limits for x and y are specified in the above using ranges. This specifies the initial grid of points for the apdaptive algorithm used by MDBM to identify solutions.
To visualize the output, we make a new method for plot and plot!. There is a distinction between 2 and 3 dimensions. Below in two dimensions curve(s) are drawn. In three dimensions, scaled cubes are used to indicate the surface.
AbstractPlotting.plot(m::MDBM_Problem; kwargs...) = plot!(Scene(), m; kwargs...) AbstractPlotting.plot!(m::MDBM_Problem; kwargs...) = plot!(AbstractPlotting.current_scene(), m; kwargs...) AbstractPlotting.plot!(scene::AbstractPlotting.Scene, m::MDBM_Problem; kwargs...) = plot!(Val(_dim(m)), scene, m; kwargs...) _dim(m::MDBM.MDBM_Problem{a,N,b,c,d,e,f,g,h}) where {a,N,b,c,d,e,f,g,h} = N
ERROR: UndefVarError: MDBM_Problem not defined
Dispatch is used for the two different dimesions, identified through _dim, defined above.
# 2D plot function AbstractPlotting.plot!(::Val{2}, scene::AbstractPlotting.Scene, m::MDBM_Problem; color=:black, kwargs...) mdt=MDBM.connect(m) for i in 1:length(mdt) dt=mdt[i] P1=getinterpolatedsolution(m.ncubes[dt[1]], m) P2=getinterpolatedsolution(m.ncubes[dt[2]], m) lines!(scene, [P1[1],P2[1]],[P1[2],P2[2]], color=color, kwargs...) end scene end
ERROR: UndefVarError: MDBM_Problem not defined
# 3D plot function AbstractPlotting.plot!(::Val{3}, scene::AbstractPlotting.Scene, m::MDBM_Problem; color=:black, kwargs...) positions = Point{3, Float32}[] scales = Vec3[] mdt=MDBM.connect(m) for i in 1:length(mdt) dt=mdt[i] P1=getinterpolatedsolution(m.ncubes[dt[1]], m) P2=getinterpolatedsolution(m.ncubes[dt[2]], m) a, b = Vec3(P1), Vec3(P2) push!(positions, Point3(P1)) push!(scales, b-a) end cube = Rect{3, Float32}(Vec3(-0.5, -0.5, -0.5), Vec3(1, 1, 1)) meshscatter!(scene, positions, marker=cube, scale = scales, color=color, transparency=true, kwargs...) scene end
ERROR: UndefVarError: MDBM_Problem not defined
We see that the equation ie has two pieces. (This is known as Newton's trident, as Newton was interested in this form of equation.)
plot(ie)
ERROR: UndefVarError: plot not defined
Plots of surfaces in 3 dimensions are useful to help understand the behavior of multivariate functions.
The "peaks" function generates the logo for MATLAB. Here we see how it can be plotted over the region $[-5,5]\times[-5,5]$.
peaks(x,y) = 3*(1-x)^2*exp(-x^2 - (y+1)^2) - 10(x/5-x^3-y^5)*exp(-x^2-y^2)- 1/3*exp(-(x+1)^2-y^2) xs = ys = range(-5, 5, length=25) <<<<<<< HEAD surface(xs, ys, peaks.(xs, ys'))
The calling pattern is surface(xs::AbstractVector, ys::AbstractVactor, zs::Matrix) which implies a rectangular grid over the $x$-$y$ plane defined by xs and ys with $z$ values given by $f(x,y)$. The using of broadcasting and a transpose yields a matrix.
Alternatively, for surface, just the function can be passed as the third value:
surface(xs, ys, peaks)
To see how a surface plot is constructed, the points $(x,y,f(x,y))$ are plotted over the grid and displayed.
Here we downsample to illustrate. The following makes a vector of point to plot with scatter (and not a matrix, difference in how the variables are specified in the comprehension):
ERROR: UndefVarError: surface not defined
The calling pattern surface(xs, ys, f) implies a rectangular grid over the $x$-$y$ plane defined by xs and ys with $z$ values given by $f(x,y)$.
Alternatively a "matrix" of $z$ values can be specified. For a function f, this is conveniently generated by the pattern f.(xs, ys'), the ' being important to get a matrix of all $x$-$y$ pairs through Julia's broadcasting syntax.
zs = peaks.(xs, ys') surface(xs, ys, zs)
ERROR: UndefVarError: surface not defined
To see how this graph is constructed, the points $(x,y,f(x,y))$ are plotted over the grid and displayed.
Here we downsample to illutrate
>>>>>>> masterxs = ys = range(-5, 5, length=5) pts = [Point3(x, y, peaks(x,y)) for x in xs for y in ys] <<<<<<< HEAD scatter(pts, markersize=250)
ERROR: UndefVarError: scatter not defined>>>>>>> master
These points are connected. The wireframe function illustrates just the frame
<<<<<<< HEAD fap = wireframe(xs, ys, peaks.(xs, ys'), linewidth=5)
ERROR: UndefVarError: wireframe not defined>>>>>>> master
The surface call triangulates the frame and fills in the shading:
<<<<<<< HEAD surface!(fap.axis, xs, ys, peaks.(xs, ys')) fap
The BeautifulMakie gallery includes an example of a surface plot with both a wireframe and 2D contour graph added. It is replicated here, in a reduced form, using the peaks function scaled by $5$.
The function and domain to plot are described by:
xs = ys = range(-3, 3, length=51) zs = peaks.(xs, ys') / 5;
The surface and wireframe are produced as follows:
fig = Figure() ax = Axis3(fig[1,1]) surface!(ax, xs, ys, zs) wireframe!(ax, xs, ys, zs, overdraw = true, transparency = true, color = (:black, 0.1)) ======= surface!(xs, ys, peaks.(xs, ys'))
ERROR: UndefVarError: surface! not defined
The set of points $(x,y,z)$ satisfying $F(x,y,z) = 0$ will form a surface that can be visualized using the MDBM package. We illustrate showing two nested surfaces.
r₂(x,y,z) = x^2 + y^2 + z^2 - 5/4 # a sphere r₄(x,y,z) = x^4 + y^4 + z^4 - 1 xs = ys = zs = -2:2 m2,m4 = implicit_equation(r₂, xs, ys, zs), implicit_equation(r₄, xs, ys, zs) plot(m4, color=:yellow) plot!(m2, color=:red)
ERROR: UndefVarError: MDBM_Problem not defined
A surface may be parametrically defined through a function $r(u,v) = (x(u,v), y(u,v), z(u,v))$. For example, the surface generated by $z=f(x,y)$ is of the form with $r(u,v) = (u,v,f(u,v))$.
The surface function and the wireframe function can be used to display such surfaces. In previous usages, the x and y values were vectors from which a 2-dimensional grid is formed. For parametric surfaces, a grid for the x and y values must be generated. This function will do so:
function parametric_grid(us, vs, r) n,m = length(us), length(vs) xs, ys, zs = zeros(n,m), zeros(n,m), zeros(n,m) for (i, uᵢ) in enumerate(us) for (j, vⱼ) in enumerate(vs) x,y,z = r(uᵢ, vⱼ) xs[i,j] = x ys[i,j] = y zs[i,j] = z end end (xs, ys, zs) end >>>>>>> master
<<<<<<< HEAD
MakieCore.Combined{Makie.wireframe, Tuple{Matrix{Float32}, Matrix{Float32}, Matrix{Float64}}}
To add the contour, a simple call via contour!(scene, xs, ys, zs) will place the contour at the $z=0$ level which will make it hard to read. Rather, placing at the "bottom" of the scene is desirable. To identify that the "scene limits" are queried and the argument transformation = (:xy, zmin) is passed to contour!:
xmin, ymin, zmin = minimum(ax.finallimits[]) contour!(ax, xs, ys, zs, levels = 15, linewidth = 2, transformation = (:xy, zmin)) fig
The transformation plot attribute sets the "plane" (one of :xy, :yz, or :xz) at a location, in this example zmin.
Suppose the task is to rotate a curve in the $x-y$ plane about the $y$ axis. The $x$ and $z$ axes will be demonstrated subsequently.
If the curve is described parametrically in the $x-y$ plane by $(g(u), f(u))$ then the surface of revolution is described by the mapping
\[ F(u,v) = \langle X(u,v), Y(u,v), Z(u,v) \rangle = \langle g(u) \cos(v), f(u), g(u)\sin(v) \rangle. \]
To plot this surface, we need to generate matrices for the X, Y, and Z functions. This can be done through a comprehension, as this example shows:
g(u) = 2 + sin(u) f(u) = cos(u) # a circle (x-2)^2 + y^2 = 1 X(u,v) = g(u)*cos(v) Y(u,v) = f(u) Z(u,v) = g(u) * sin(v) us = range(-pi, pi, length=5) vs = range(0, 2pi, length=5) Xs = [X(u,v) for u in us, v in vs] # makes a matrix Ys = [Y(u,v) for u in us, v in vs] # makes a matrix Zs = [Z(u,v) for u in us, v in vs] # makes a matrix
5×5 Matrix{Float64}:
0.0 2.0 2.44929e-16 -2.0 -4.89859e-16
0.0 1.0 1.22465e-16 -1.0 -2.44929e-16
0.0 2.0 2.44929e-16 -2.0 -4.89859e-16
0.0 3.0 3.67394e-16 -3.0 -7.34788e-16
0.0 2.0 2.44929e-16 -2.0 -4.89859e-16
This can then be plotted using surface, as with
surface(Xs, Ys, Zs)
Of course, taking more values for us and vs would smooth out the surface.
Before doing so, we would like to streamline the production of the matrices Xs, Ys, Zs which are all similarly produced.
We first create one function to hold the definitions of X, Y, and Z:
F(u,v) = [g(u)*cos(v), f(u), g(u)*sin(v)]
F (generic function with 1 method)
Then, we use broadcasting to create a matrix of points:
F.(us, vs') # vs' makes a matrix
5×5 Matrix{Vector{Float64}}:
[2.0, -1.0, 0.0] … [2.0, -1.0, -4.89859e-16]
[1.0, 6.12323e-17, 0.0] [1.0, 6.12323e-17, -2.44929e-16]
[2.0, 1.0, 0.0] [2.0, 1.0, -4.89859e-16]
[3.0, 6.12323e-17, 0.0] [3.0, 6.12323e-17, -7.34788e-16]
[2.0, -1.0, 0.0] [2.0, -1.0, -4.89859e-16]
Then we use unzip to turn a matrix of points into three matrices. This shows just Zs for comparison:
unzip(F.(us, vs'))[3]
5×5 Matrix{Float64}:
0.0 2.0 2.44929e-16 -2.0 -4.89859e-16
0.0 1.0 1.22465e-16 -1.0 -2.44929e-16
0.0 2.0 2.44929e-16 -2.0 -4.89859e-16
0.0 3.0 3.67394e-16 -3.0 -7.34788e-16
0.0 2.0 2.44929e-16 -2.0 -4.89859e-16
With this, then we can create the same graph with fewer lines:
surface(unzip(F.(us, vs'))...)
Finally, adding more values we get a better graph:
F(u,v) = [g(u)*cos(v), f(u), g(u)*sin(v)] us = range(-pi, pi, length=50) vs = range(0, 2pi, length=50) fap = surface(unzip(F.(us, vs'))...) lines!([Point3(0, -3, 0), Point3(0, 3, 0)], linewidth=10) fap
The last lines! command adds the $y$ axis to illustrate the rotation.
rotation about the $x$ axes: The function F(u,v) = [g(u), f(u)*cos(v), f(u)*sin(v)] is used.
The usages will be similar. In this graph we emphasize the $x$ axis:
F(u,v) = [g(u), f(u)*cos(v), f(u)*sin(v)] us = range(-pi, pi, length=50) vs = range(0, 2pi, length=50) fap = surface(unzip(F.(us, vs'))...) lines!([Point3(0, 0, 0), Point3(4, 0, 0)], linewidth=10) fap
rotation about the $z$ axis is also possible. To rotate a line in the $x-z$ plane (not the $x-y$ plane) described parametrically by $(g(u), f(u))$ we use F(u,v) = [g(u)*cos(v), g(u)*sin(v), f(u)].
For example, the torus again:
F(u,v) = [g(u)*cos(v), g(u)*sin(v), f(u)] us = range(-pi, pi, length=50) vs = range(0, 2pi, length=50) fap = surface(unzip(F.(us, vs'))...) lines!([Point3(0, 0, -2), Point3(0, 0, 2)], linewidth=10) fap
Surfaces of revolution are a special case of a parametrically defined defined surface, which would have $r(u,v) = (x(u,v), y(u,v), z(u,v))$. This covers the two cases discussed above: the surface $z=f(x,y)$ is of the form with $r(u,v) = (u,v,f(u,v))$ and, as just seen, the surfaces of revolution have this form.
The surface function and the wireframe function are used to display such surfaces. The approach is identical to that for a surface of revolution – generate matrices for $x$, $y$, and $z$ and plot these with surface or wireframe.
For example, a sphere can be parameterized by $r(u,v) = (\sin(u)\cos(v), \sin(u)\sin(v), \cos(u))$ and visualized through:
r(u,v) = [sin(u)*cos(v), sin(u)*sin(v), cos(u)] us = range(0, pi, length=25) vs = range(0, pi/2, length=25) xs,ys,zs = unzip(r.(us, vs')) fap = surface(xs, ys, zs) wireframe!(xs, ys, zs, linewidth=5) fap
A Möbius strip can be produced with:
ws = range(-1/4, 1/4, length=8) thetas = range(0, 2pi, length=30) r(w, θ) = ((1+w*cos(θ/2))*cos(θ), (1+w*cos(θ/2))*sin(θ), w*sin(θ/2)) xs, ys, zs = unzip(r.(ws, thetas')) fap = surface(xs, ys, zs) wireframe!(xs, ys, zs) fap
With the data suitably massaged, we can directly plot either a surface or wireframe plot.
As an aside, The above can be done more campactly with nested list comprehensions:
xs, ys, zs = [[pt[i] for pt in r.(us, vs')] for i in 1:3]
Or using the unzip function directly after broadcasting:
xs, ys, zs = unzip(r.(us, vs'))
For example, a sphere can be parameterized by $r(u,v) = (\sin(u)\cos(v), \sin(u)\sin(v), \cos(u))$ and visualized through:
r(u,v) = [sin(u)*cos(v), sin(u)*sin(v), cos(u)] us = range(0, pi, length=25) vs = range(0, pi/2, length=25) xs, ys, zs = parametric_grid(us, vs, r) scene = Scene() surface!(scene, xs, ys, zs) wireframe!(scene, xs, ys, zs)
ERROR: UndefVarError: Scene not defined
A surface of revolution for $g(u)$ revolved about the $z$ axis can be visualized through:
g(u) = u^2 * exp(-u) r(u,v) = (g(u)*sin(v), g(u)*cos(v), u) us = range(0, 3, length=10) vs = range(0, 2pi, length=10) xs, ys, zs = parametric_grid(us, vs, r) scene = Scene() surface!(scene, xs, ys, zs) wireframe!(scene, xs, ys, zs)
ERROR: UndefVarError: Scene not defined
A torus with big radius $2$ and inner radius $1/2$ can be visualized as follows
r1, r2 = 2, 1/2 r(u,v) = ((r1 + r2*cos(v))*cos(u), (r1 + r2*cos(v))*sin(u), r2*sin(v)) us = vs = range(0, 2pi, length=25) xs, ys, zs = parametric_grid(us, vs, r) scene = Scene() surface!(scene, xs, ys, zs) wireframe!(scene, xs, ys, zs)
ERROR: UndefVarError: Scene not defined
A Möbius strip can be produced with:
ws = range(-1/4, 1/4, length=8) thetas = range(0, 2pi, length=30) r(w, θ) = ((1+w*cos(θ/2))*cos(θ), (1+w*cos(θ/2))*sin(θ), w*sin(θ/2)) xs, ys, zs = parametric_grid(ws, thetas, r) scene = Scene() surface!(scene, xs, ys, zs) wireframe!(scene, xs, ys, zs)
ERROR: UndefVarError: Scene not defined
contour, heatmap)For a function $z = f(x,y)$ an alternative to a surface plot, is a contour plot. That is, for different values of $c$ the level curves $f(x,y)=c$ are drawn.
For a function $f(x,y)$, the syntax for generating a contour plot follows that for surface.
For example, using the peaks function, previously defined, we have a contour plot over the region $[-5,5]\times[-5,5]$ is generated through:
xs = ys = range(-5, 5, length=100) contour(xs, ys, peaks)
ERROR: UndefVarError: contour not defined
The default of $5$ levels can be adjusted using the levels keyword:
contour(xs, ys, peaks.(xs, ys'), levels = 20)
ERROR: UndefVarError: contour not defined
(As a reminder, the above also shows how to generate values "zs" to pass to contour instead of a function.)
The contour graph makes identification of peaks and valleys easy as the limits of patterns of nested contour lines.
An alternative visualzation using color to replace contour lines is a heatmap. The heatmap function produces these. The calling syntax is similar to contour and surface:
heatmap(xs, ys, peaks)
ERROR: UndefVarError: heatmap not defined
This graph shows peaks and valleys through "hotspots" on the graph.
The MakieGallery package includes an example of a surface plot with both a wireframe and 2D contour graph added. It is replicated here using the peaks function scaled by $5$.
The function and domain to plot are described by:
xs = ys = range(-5, 5, length=51) zs = peaks.(xs, ys') / 5;
The zs were generated, as wireframe does not provide the interface for passing a function.
The surface and wireframe are produced as follows:
scene = surface(xs, ys, zs) wireframe!(scene, xs, ys, zs, overdraw = true, transparency = true, color = (:black, 0.1))
ERROR: UndefVarError: surface not defined
To add the contour, a simple call via contour!(scene, xs, ys, zs) will place the contour at the $z=0$ level which will make it hard to read. Rather, placing at the "bottom" of the scene is desirable. To identify that the "scene limits" are queried and the argument transformation = (:xy, zmin) is passed to contour!:
xmin, ymin, zmin = minimum(scene_limits(scene)) contour!(scene, xs, ys, zs, levels = 15, linewidth = 2, transformation = (:xy, zmin)) center!(scene)
ERROR: UndefVarError: scene_limits not defined
The transformation plot attribute sets the "plane" (one of :xy, :yz, or :xz) at a location, in this example zmin.
The contour function can also plot $3$-dimensional contour plots. Concentric spheres, contours of $x^2 + y^2 + z^2 = c$ for $c > 0$ are presented by the following:
f(x,y,z) = x^2 + y^2 + z^2 xs = ys = zs = range(-3, 3, length=100) contour(xs, ys, zs, f)<<<<<<< HEAD
ERROR: UndefVarError: contour not defined>>>>>>> master
The vector field $f(x,y) = (y, -x)$ can be visualized as a set of vectors, $f(x,y)$, positioned at a grid. These can be produced with the arrows function. Below we pass a vector of points for the anchors and a vector of points representing the vectors.
We can generate these on a regular grid through:
f(x, y) = [y, -x] xs = ys = -5:5 pts = vec(Point2.(xs, ys')) dus = vec(Point2.(f.(xs, ys')))
121-element Vector{GeometryBasics.Point2{Int64}}:
[-5, 5]
[-5, 4]
[-5, 3]
[-5, 2]
[-5, 1]
[-5, 0]
[-5, -1]
[-5, -2]
[-5, -3]
[-5, -4]
⋮
[5, 3]
[5, 2]
[5, 1]
[5, 0]
[5, -1]
[5, -2]
[5, -3]
[5, -4]
[5, -5]
Broadcasting over (xs, ys') ensures each pair of possible values is encountered. The vec call reshapes an array into a vector.
Calling arrows on the prepared data produces the graphic:
arrows(pts, dus)<<<<<<< HEAD
ERROR: UndefVarError: arrows not defined>>>>>>> master
The grid seems rotated at first glance. This is due to the length of the vectors as the $(x,y)$ values get farther from the origin. Plotting the normalized values (each will have length $1$) can be done easily using norm (which requires LinearAlgebra to be loaded):
using LinearAlgebra dvs = dus ./ norm.(dus) arrows(pts, dvs)<<<<<<< HEAD
ERROR: UndefVarError: arrows not defined>>>>>>> master
The rotational pattern becomes quite clear now.
The streamplot function also illustrates this phenomenon. This implements an "algorithm [that] puts an arrow somewhere and extends the streamline in both directions from there. Then, it chooses a new position (from the remaining ones), repeating the the exercise until the streamline gets blocked, from which on a new starting point, the process repeats."
The streamplot function expects a point not a pair of values, so we adjust f slightly and call the function using the pattern streamplot(f, xs, ys):
<<<<<<< HEAD g(p) = Point2(f(p[1], p[2])) ======= g(x,y) = Point2(f(x,y)) >>>>>>> master streamplot(g, xs, ys)<<<<<<< HEAD
(The viewing range could also be adjusted with the -5..5 notation from the IntervalSets package which is brought in when AbstractPlotting is loaded.)
Interaction with a scene is very much integrated into Makie, as the design has a "sophisticated referencing system" which allows sharing of attributes. Adjusting one attribute, can then propagate to others. This allows figures to be modified without the need to redraw all of the non-modified components.
In Makie, a Node is a structure that allows its value to be updated, similar to an array. Nodes are Observables, which when changed can trigger an event. Nodes can rely on other nodes, so events can be cascaded.
A simple example is a means to dynamically adjust a label for a scene.
xs, ys = 1:5, rand(5) fig, ax, plt = scatter(xs, ys)
We can create a "Node" through:
x = Node("x values")
Observable{String} with 0 listeners. Value:
"x values"
The value of the node is retrieved by x[], though the function call to_value(x) is recommened, as it will be defined even when x is not a node. This stored value could be used to set the $x$-label in our scene:
ax.xlabel = x[]
"x values"
We now set up an observer to update the label whenever the value of x is updated:
on(x) do val ax.xlabel = val end
(::Observables.ObserverFunction) (generic function with 0 methods)
Now setting the value of x will also update the label:
x[] = "The x axis"
"The x axis"
When setting a node, the value on the right-hand side must be able to be converted to the type specified for the node on construction. (You can't assign a number to the x above, for example.)
A node can be more complicated. This shows how a node of $x$ value can be used to define dependent $y$ values. A scatter plot will update when the $x$ values are updated:
xs = Node(1:10) ys = lift(a -> f.(a), xs)
Observable{Vector{Float64}} with 0 listeners. Value:
[0.5403023058681398, -0.4161468365471424, -0.9899924966004454, -0.6536436208636119, 0.28366218546322625, 0.960170286650366, 0.7539022543433046, -0.14550003380861354, -0.9111302618846769, -0.8390715290764524]
The lift function lifts the values of xs to the values of ys.
These can be plotted directly:
fig, ax, plt = lines(xs, ys)
Changes to the xs values will be reflected in the scene.
xs[] = 2:9
2:9
We can incoporporate the two:
lab = lift(val -> "Showing from $(val.start) to $(val.stop)", xs) on(lab) do val ax.xlabel = val end
(::Observables.ObserverFunction) (generic function with 0 methods)
xs[] = 1:100 fig
ERROR: UndefVarError: streamplot not defined
(The viewing range could also be adjusted with the -5..5 notation from the IntervalSets package which is brought in when AbstractPlotting is loaded.)
Interaction with a scene is very much integrated into Makie, as the design has a "sophisticated referencing system" which allows sharing of attributes. Adjusting one attribute, can then propogate to others.
In Makie, a Node is a structure that allows its value to be updated, similar to an array. Nodes are Observables, which when changed can trigger an event. Nodes can rely on other nodes, so events can be cascaded.
A simple example is a means to dynamically adjust a label for a scene.
xs, = 1:5, rand(5)
scene = scatter(xs, ys)
We can create a "Node" through:
x = Node("x values")
The value of the node is retrieved by x[], though the function call to_value(x) is recommened, as it will be defined even when x is not a node. This stored value could be used to set the $x$-label in our scene:
xlabel!(scene, x[])
We now set up an observer to update the label whenever the value of x is updated:
on(x) do val
xlabel!(scen, val)
end
Now setting the value of x will also update the label:
x[] = "The x axis"
A node can be more complicated. This shows how a node of $x$ value can be used to define dependent $y$ values. A scatter plot will update when the $x$ values are updated:
xs = Node(1:10)
ys = lift(a -> f.(a), xs)
The lift function lifts the values of xs to the values of ys.
These can be plotted directly:
scene = lines(xs, ys)
Changes to the xs values will be reflected in the scene.
xs[] = 2:9
We can incoporporate the two:
lab = lift(val -> "Showing from $(val.start) to $(val.stop)", xs)
on(lab) do val
xlabel!(scene, val)
udpate!(scene)
end
The update! call redraws the scene to adjust to increased or decreased range of $x$ values.
The mouse position can be recorded. An example in the gallery of examples shows how.
Here is a hint:
scene = lines(1:5, rand(5))
pos = lift(scene.events.mouseposition) do mpos
@show AbstractPlotting.to_world(scene, Point2f0(mpos))
end
This will display the coordinates of the mouse in the terminal, as the mouse is moved around.
>>>>>>> master